package com.cb.auth;

import java.util.List;

import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import com.cb.admin.domain.UserInfo;
import com.cb.admin.service.IUserInfoService;

/**
 * Realm 是可以访问程序特定的安全数据如用户、角色、权限等的一个组件。
 * @author Administrator
 *
 */
public class AuthRealm extends AuthorizingRealm{
	
	private static Logger logger = Logger.getLogger(AuthRealm.class.getName());
	
    @Autowired
    private IUserInfoService userInfoService;
    
    /**
     * 授权用户权限
     * 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
     * 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
     * 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
     *
     * shiro的权限授权是通过继承AuthorizingRealm抽象类，重载doGetAuthorizationInfo();
     * 当访问到页面的时候，链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
     * 所以如果只是简单的身份认证没有权限的控制的话，那么这个方法可以不进行实现，直接返回null即可。
     *
     * 在这个方法中主要是使用类：SimpleAuthorizationInfo 进行角色的添加和权限的添加。
     * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
     *
     * 当然也可以添加set集合：roles是从数据库查询的当前用户的角色，stringPermissions是从数据库查询的当前用户对应的权限
     * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
     *
     * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
     * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
     *
     * 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002]，perms[权限添加]");
     * 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    	logger.info("AuthRealm.doGetAuthorizationInfo() : 数据库获取权限 >> ");
    	//获取前端输入的用户信息，封装为User对象
    	UserInfo user = (UserInfo) SecurityUtils.getSubject().getPrincipal();
        //获取前端输入的用户名
        String userName = user.getAccount();
        //根据前端输入的用户名查询数据库中对应的记录
        //如果数据库中有该用户名对应的记录，就进行授权操作
        if (user != null){
            //获取用户角色
            List<String> roles = userInfoService.queryUserRoleByName(userName);
            //添加角色
            SimpleAuthorizationInfo authorizationInfo =  new SimpleAuthorizationInfo();
            for (String role : roles) {
                authorizationInfo.addRole(role);
            }

            //获取用户权限
            List<String> permissions = userInfoService.queryRoleMemuByName(roles);
            //添加权限
            for (String permission:permissions) {
                authorizationInfo.addStringPermission(permission);
            }
            
            return authorizationInfo;
        }else{
            return null;
        }

    }

    /**
     * 认证登录
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    	logger.info("AuthRealm.doGetAuthenticationInfo() : 数据库获取登录认证信息 >> ");
    	//token携带了用户信息
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        //获取前端输入的用户名
        String userName  = usernamePasswordToken.getUsername();
        //根据用户名查询数据库中对应的记录
        UserInfo user = userInfoService.queryByAP(userName);
        //当前realm对象的name
        String realmName = getName();
        
        SimpleAuthenticationInfo  authcInfo;
        
        if(user!=null){
        	//盐值
            ByteSource credentialsSalt = ByteSource.Util.bytes(user.getAccount());
            //封装用户信息，构建AuthenticationInfo对象并返回
            //注意，数据库中的user的密码必须是要经过md5加密，不然还是会抛出异常！！！！！
            authcInfo = new SimpleAuthenticationInfo(user, 
            		user.getPwd(), credentialsSalt, realmName);
        }else{
        	return null;
        }
        
        return authcInfo;
    }
    
    /**
     * 重写方法,清除当前用户的的 授权缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    /**
     * 重写方法，清除当前用户的 认证缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    /**
     * 自定义方法：清除所有 授权缓存
     */
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    /**
     * 自定义方法：清除所有 认证缓存
     */
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    /**
     * 自定义方法：清除所有的  认证缓存  和 授权缓存
     */
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }
    
    /**
	 * 明文进行谜面进行加密
	 * @param args
	 */
	public static void main(String[] args) {
		int hashIterations = 1024;//加密的次数
		ByteSource credentialsSalt = ByteSource.Util.bytes("admin");
		Object credentials = "admin";//密码
		String hashAlgorithmName = "MD5";//加密方式
		Object simpleHash = new SimpleHash(hashAlgorithmName, credentials,
				credentialsSalt, hashIterations);
		System.out.println("加密后的值----->" + simpleHash);
	}
}